/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.IO;
using System.Text;
using NDODS.DAP.BaseTypes;
using NDODS.DAP.Exceptions;
using NDODS.DAP.Parser;
using NDODS.Util;


namespace NDODS.DAP.Server
{
    /// <summary> This class is used to parse and Evaluate a constraint expression. When
    /// constructed it must be passed a valid DDS along with the expression. This
    /// DDS will be used as the environment (collection of variables and
    /// functions) during the parse and evaluation of the constraint expression.
    /// <p/>
    /// A server (servlet, CGI, ...) must first instantiate the DDS (possibly
    /// reading it from a cache) and then create and instance of this class. Once
    /// created, the constraint may be parsed and then evaluated. The class
    /// supports sending data based on the results of CE evaluation. That is, the
    /// send() method of the class combines both the evaluation of the constraint
    /// and the output of data values so that the server can return data using a
    /// single method call.<p>
    /// <p/>
    /// <b>Custom parsing</b>
    /// The CEEvaluator parses constraint expressions into Clause objects
    /// using a ClauseFactory. Customized behavior during parsing can be
    /// achieved by passing a customized ClauseFactory into the CEEvaluator.<p>
    /// <p/>
    /// <b>Support for server side functions</b>
    /// Server side functions are supported via the FunctionLibrary class.
    /// Custom server side function support is achieved by using
    /// a customized ClauseFactory which in turn contains a customized
    /// FunctionLibrary.<p>
    /// <p/>
    /// More details are found in the documentation for the respective classes.
    /// 
    /// </summary>
    /// <author>  jhrg
    /// </author>
    /// <author>  ndp
    /// </author>
    /// <author>  Joe Wielgosz (joew@cola.iges.org)
    /// </author>
    /// <version>  $Revision: 15901 $
    /// </version>
    /// <seealso cref="ServerDDS">
    /// </seealso>
    /// <seealso cref="ServerMethods">
    /// </seealso>
    /// <seealso cref="ClauseFactory">
    /// </seealso>
    /// <seealso cref="FunctionLibrary">
    /// </seealso>
    /// <seealso cref="Clause">
    /// </seealso>
    public class CEEvaluator
    {
        /// <summary> Return a reference to the CEEvaluator's DDS object.</summary>
        public virtual ServerDDS DDS
        {
            get { return _dds; }
        }

        /// <summary> Get access to the list of clauses built by parsing the selection part
        /// of the constraint expression.
        /// <p/>
        /// NB: This is not valid until the CE has been parsed!
        /// </summary>
        public virtual IEnumerator Clauses
        {
            get { return _cv.GetEnumerator(); }
        }

        private static bool _Debug;

        /// <summary> This contains the DDS to be used during parse and evaluation of the
        /// CE.
        /// </summary>
        private ServerDDS _dds;

        /// <summary> The Clause objects which hold the parsed selection information.</summary>
        private ArrayList _cv;

        /// <summary> The factory which will be used by the parser to construct the clause
        /// tree. This allows servers to pass in a factory which creates
        /// custom clause objects.
        /// </summary>
        private ClauseFactory clauseFactory;

        /// <summary> Construct a new <code>CEEvaluator</code> with <code>dds</code> as the
        /// DDS object with which to resolve all variable and function names.
        /// 
        /// </summary>
        /// <param name="dds">DDS object describing the dataset targeted by this
        /// constraint.
        /// </param>
        public CEEvaluator(ServerDDS dds)
        {
            _dds = dds;
            _cv = ArrayList.Synchronized(new ArrayList(10));
            _Debug = Debug.isSet("CE");
            if (_Debug)
                Console.Out.WriteLine("CE debugging enabled.");
        }

        /// <summary> Construct a new <code>CEEvaluator</code> with <code>dds</code> as the
        /// DDS object with which to resolve all variable and function names, and
        /// <code>clauseFactory</code> as a source of Clause objects .
        /// 
        /// </summary>
        /// <param name="clauseFactory">The factory which will be used by the parser to construct the clause
        /// tree. This allows servers to pass in a factory which creates
        /// custom clause objects.
        /// </param>
        /// <param name="dds">          DDS object describing the dataset targeted by this
        /// constraint.
        /// </param>
        public CEEvaluator(ServerDDS dds, ClauseFactory clauseFactory)
        {
            _dds = dds;
            _cv = ArrayList.Synchronized(new ArrayList(10));
            _Debug = Debug.isSet("CE");
            if (_Debug)
                Console.Out.WriteLine("CE debugging enabled.");

            this.clauseFactory = clauseFactory;

            _Debug = Debug.isSet("CEEvaluator");
        }

        /// <summary> Parse a constraint expression. Variables in the projection are marked
        /// as such in the CEEvaluator's ServerDDS instance. The selection
        /// subexpression is then parsed and a list of Clause objects is built.
        /// <p/>
        /// The parser is located in NDODS.DAP.Parser.ExprParser.
        /// 
        /// </summary>
        /// <param name="expression">The constraint expression to parse.
        /// </param>
        /// <throws>  ParseException </throws>
        /// <throws>  NoSuchVariableException </throws>
        /// <throws>  NoSuchFunctionException </throws>
        /// <throws>  InvalidOperatorException </throws>
        /// <throws>  InvalidParameterException </throws>
        /// <throws>  SBHException </throws>
        public virtual void parseConstraint(String expression)
        {
            MemoryStream sExpr = new MemoryStream(new UnicodeEncoding().GetBytes(expression));
            ExprParser exp = new ExprParser(sExpr);


            if (clauseFactory == null)
            {
                clauseFactory = new ClauseFactory();
            }

            try
            {
                // Parses constraint expression (duh...) and sets the
                // projection flag for each member of the CE's ServerDDS
                // instance. This also builds the list of clauses.
                exp.constraint_expression(this, _dds.Factory, clauseFactory);
            }
            catch (TokenMgrError tme)
            {
                //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.getMessage' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                throw new ParseException(tme.Message);
            }


            if (_Debug)
            {
                int it = 0;
                IEnumerator ec = Clauses;

                Console.Out.WriteLine("Results of clause parsing:");
                //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                if (!ec.MoveNext())
                    Console.Out.WriteLine("    No Clauses Found.");

                //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                while (ec.MoveNext())
                {
                    it++;
                    //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Object.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                    //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                    Console.Out.WriteLine("    Clause " + it + ": " + ec.Current);
                }
            }
        }

        /// <summary> Add a clause to the constraint expression.
        /// 
        /// </summary>
        /// <param name="c">The Clause to append.
        /// </param>
        public virtual void appendClause(IClause c)
        {
            if (c != null)
            {
                _cv.Add(c);
            }
        }

        /// <summary> Remove a clause from the constraint expression. This will
        /// will remove the first occurence of the passed clause from
        /// the constraint expression. This is done be reference, so if
        /// the passed Clause object is NOT already in the constraint
        /// expression then nothing happens. And, if it should appear
        /// more than once (which I <b>don't</b> think is possible) only
        /// the first occurence will be removed.
        /// 
        /// </summary>
        /// <param name="c">The Clause to append.
        /// </param>
        /// <returns> True if constraint expression contained the passed Clause
        /// object and it was successfully removed.
        /// </returns>
        public virtual bool removeClause(IClause c)
        {
            if (c != null)
            {
                Boolean tempBoolean;
                tempBoolean = _cv.Contains(c);
                _cv.Remove(c);
                return (tempBoolean);
            }
            return (false);
        }


        /// <summary> This function sends the variables described in the constrained DDS to
        /// the output described by <code>sink</code>. This function calls
        /// <code>parse_constraint()</code>, <code>BaseType::Read()</code>, and
        /// <code>ServerIO::serialize()</code>.
        /// 
        /// </summary>
        /// <param name="dataset"> The name of the dataset to send.
        /// </param>
        /// <param name="sink">    A pointer to the output buffer for the data.
        /// </param>
        /// <param name="specialO">An <code>Object</code> passed by the server. This is typically used by server implementations
        /// to deliver needed functionaliy or information to the Read methods of each <code>BaseType</code>.
        /// </param>
        /// <seealso cref="parseConstraint(String)">
        /// </seealso>
        /// <seealso cref="ServerMethods.serialize(String, DataOutputStream,">
        /// CEEvaluator, Object) ServerMethods.serialize()
        /// </seealso>
        public virtual void send(String dataset, Stream sink, Object specialO)
        {
            IEnumerator e = _dds.Variables;
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            while (e.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                IServerMethods s = (IServerMethods) e.Current;

                if (_Debug)
                    Console.Out.WriteLine("Sending variable: " + ((BaseType) s).Name);

                if (s.isProject())
                {
                    if (_Debug)
                        Console.Out.WriteLine("Calling " + ((BaseType) s).TypeName + ".serialize() (Name: " +
                                              ((BaseType) s).Name + ")");

                    //UPGRADE_TODO: Class 'java.io.DataOutputStream' was converted to 'System.IO.BinaryWriter' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioDataOutputStream'"
                    s.serialize(dataset, new BinaryWriter(sink), this, specialO);
                }
            }
        }


        /// <summary> Evaluate all of the Clauses in the Clause vector.
        /// 
        /// </summary>
        /// <param name="specialO">That special Object that can be passed down
        /// through the <code>DDS.send()</code> method.
        /// </param>
        /// <returns> True if all the Clauses Evaluate to true, false otherwise.
        /// </returns>
        public virtual bool evalClauses(Object specialO)
        {
            bool result = true;
            IEnumerator ec = Clauses;

            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            while (ec.MoveNext() && result == true)
            {
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                Object o = ec.Current;
                if (_Debug)
                {
                    //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Object.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                    //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                    Console.Out.WriteLine("Evaluating clause: " + ec.Current);
                }

                result = ((ITopLevelClause) o).Evaluate();
            }

            return (result);
        }


        /// <summary> Mark all the variables in the DDS either as part of the current
        /// projection (when <code>state</code> is true) or not
        /// (<code>state</code> is false). This is a convenience function that
        /// provides a way to clear or set an entire dataset described by a DDS
        /// with respect to its projection.
        /// 
        /// </summary>
        /// <param name="state">true if the variables should all be projected, false is
        /// no variable should be projected.
        /// </param>
        public virtual void markAll(bool state)
        {
            // For all the Variables in the DDS
            IEnumerator e = _dds.Variables;
            //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
            while (e.MoveNext())
            {
                // Get the thing
                //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                Object o = e.Current;
                //- Clip this to stop marking all dimensions of Grids and Arrays
                // If we are marking all for true, then we need to make sure
                // we get all the parts of each array and grid

                // This code should probably be moved into SDArray and SDGrid.
                // There we should add a resetProjections() method that changes
                // the current projection to be the entire array. 11/18/99 jhrg
                if (state)
                {
                    if (o is SDArray)
                    {
                        // Is this thing a SDArray?
                        SDArray SDA = (SDArray) o;

                        // Get it's DArrayDimensions
                        IEnumerator eSDA = SDA.Dimensions;
                        //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                        while (eSDA.MoveNext())
                        {
                            //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                            DArrayDimension dad = (DArrayDimension) eSDA.Current;
                            // Tweak it's projection state
                            dad.setProjection(0, 1, dad.Size - 1);
                        }
                    }
                    else if (o is SDGrid)
                    {
                        // Is this thing a SDGrid?
                        SDGrid SDG = (SDGrid) o;
                        SDArray sdgA = (SDArray) SDG.getVar(0); // Get it's internal SDArray.

                        // Get it's DArrayDimensions
                        IEnumerator eSDA = sdgA.Dimensions;
                        //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationhasMoreElements'"
                        while (eSDA.MoveNext())
                        {
                            //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilEnumerationnextElement'"
                            DArrayDimension dad = (DArrayDimension) eSDA.Current;
                            // Tweak it's projection state
                            dad.setProjection(0, 1, dad.Size - 1);
                        }
                    }
                }
                //-------------------------- End Clip ---------------------------

                IServerMethods s = (IServerMethods) o;
                s.setProject(state);
            }
        }
    }
}